// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Configuration.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.LiveObjects
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;

    using Org.OpenTrader.Framework.LiveObjects.Interfaces;

    #endregion

    /// <summary>
    /// The configuration.
    /// </summary>
    [Serializable]
    public sealed class Configuration : IConfiguration
    {
        private static readonly Identity Identity = Identity.Create(System.Reflection.MethodBase.GetCurrentMethod());

        #region Constants and Fields

        /// <summary>
        /// The configurationSchema.
        /// </summary>
        private IConfigurationSchema configurationSchema;

        /// <summary>
        /// The container.
        /// </summary>
        private ILiveObjectContainer container;

        /// <summary>
        /// The fields.
        /// </summary>
        private Dictionary<string, ConfigurationField> fields;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Configuration"/> class.
        /// </summary>
        /// <param name="id">
        /// The id.
        /// </param>
        /// <param name="schema">
        /// The schema.
        /// </param>
        /// <param name="container">
        /// The container.
        /// </param>
        public Configuration(Guid id, IConfigurationSchema schema, ILiveObjectContainer container)
        {
            if (container == null)
            {
                throw new Exception(string.Empty);
            }

            this.ID = id;
            this.fields = new Dictionary<string, ConfigurationField>();
            this.container = container;
            this.configurationSchema = schema;
            this.SetupConfigurationSchema();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets ConfigurationSchema.
        /// </summary>
        /// <exception cref="Exception">
        /// </exception>
        public IConfigurationSchema ConfigurationSchema
        {
            get
            {
                return this.configurationSchema;
            }
        }

        /// <summary>
        /// Gets or sets ID.
        /// </summary>
        public Guid ID { get; set; }

        #endregion

        #region Indexers

        /// <summary>
        /// The this.
        /// </summary>
        /// <param name="fieldName">
        /// The fieldName.
        /// </param>
        /// <exception cref="ArgumentException">
        /// </exception>
        public ConfigurationField this[string fieldName]
        {
            get
            {
                if (this.fields.ContainsKey(fieldName))
                {
                    var fieldValue = this.fields[fieldName];
                    return fieldValue;
                }
                else
                {
                    throw new ArgumentException(string.Format("The field '{0}' is unknown", fieldName));
                }
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The dispose.
        /// </summary>
        public void Dispose()
        {
        }

        /// <summary>
        /// The to string.
        /// </summary>
        /// <returns>
        /// The to string.
        /// </returns>
        public override string ToString()
        {
            var s = string.Empty;
            foreach (var v in this.fields)
            {
                s += string.Format("{0}={1}", v.Key, v.Value);
            }

            return s;
        }

        #endregion

        #region Implemented Interfaces

        #region IFlatFileExportImport

        /// <summary>
        /// The de serialize.
        /// </summary>
        /// <param name="reader">
        /// The reader.
        /// </param>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public Status DeSerialize(StreamReader reader, ISerializationContext context)
        {
            string line = null;
            while ((line = reader.ReadLine()) != null)
            {
                if (line.Equals(string.Empty))
                {
                    continue;
                }

                var record = line.Split('=');
                var fieldName = record[0];
                var fieldValue = record[1];

                var fieldDescriptor = this.ConfigurationSchema.GetFieldDescriptor(fieldName);
                if (fieldDescriptor != null)
                {
                    if (Reflection.ImplementsInterface(fieldDescriptor.Type, typeof(IFlatFileExportImport).FullName))
                    {
                        var memoryStream = new MemoryStream(UTF8Encoding.UTF8.GetBytes(fieldValue));
                        var streamReader = new StreamReader(memoryStream);
                        var currentValue = this[fieldName] as IFlatFileExportImport;
                        var status = currentValue.DeSerialize(streamReader, context);
                        if (!status)
                        {
                            return Status.Failure(Identity, string.Format("Error while deserializing {0}", fieldName), status);
                        }
                    }
                }
            }

            return Status.Success(Identity);
        }

        /// <summary>
        /// The serialize.
        /// </summary>
        /// <param name="writer">
        /// The writer.
        /// </param>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <returns>
        /// </returns>
        public Status Serialize(StreamWriter writer, ISerializationContext context)
        {
            foreach (var fieldName in this.fields.Keys)
            {
                writer.Write(string.Format("{0}=", fieldName));
                var fieldDescriptor = this.ConfigurationSchema.GetFieldDescriptor(fieldName);
                var fieldContent = this.fields[fieldName];

                if (Reflection.ImplementsInterface(fieldDescriptor.Type, typeof(IFlatFileExportImport).FullName))
                {
                    var serializable = this[fieldName] as IFlatFileExportImport;
                    var status = serializable.Serialize(writer, context);
                    if (!status)
                    {
                        return Status.Failure(Identity, string.Format("Failed to serialize field {0}", fieldName), status);
                    }
                }
            }

            // All done
            return Status.Success(Identity);
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The setup bool.
        /// </summary>
        /// <param name="fieldName">
        /// The field name.
        /// </param>
        /// <param name="attribute">
        /// The attribute.
        /// </param>
        /// <exception cref="Exception">
        /// </exception>
        private void SetupBool(string fieldName, ConfigurationFieldAttribute attribute)
        {
            if (attribute.IsMandatory)
            {
                if (attribute.DefaultValue == null)
                {
                    throw new Exception("You must provide a DefaultValue for mandatory field of type ConfigurationField.Bool");
                }

                if (!attribute.DefaultValue.GetType().Equals(typeof(bool)))
                {
                    throw new Exception("DefaultValue must have the bool type when ConfigurationField.Bool is used");
                }

                this.fields[fieldName] = new ConfigurationField.Bool(attribute.IsMandatory, (bool)attribute.DefaultValue);
            }
            else
            {
                this.fields[fieldName] = new ConfigurationField.Bool(attribute.IsMandatory);
            }
        }

        /// <summary>
        /// The setup configuration schema.
        /// </summary>
        private void SetupConfigurationSchema()
        {
            // Setup all field, those having a default value are taken as is, others are constructed
            foreach (var fieldName in this.configurationSchema.Fields)
            {
                var attribute = this.configurationSchema.GetFieldDescriptor(fieldName);

                if (attribute.Type.Equals(typeof(ConfigurationField.Enum)))
                {
                    this.SetupEnum(fieldName, attribute);
                }
                else if (attribute.Type.Equals(typeof(ConfigurationField.Bool)))
                {
                    this.SetupBool(fieldName, attribute);
                }
                else if (attribute.Type.Equals(typeof(ConfigurationField.Int)))
                {
                    this.SetupInt(fieldName, attribute);
                }
                else if (attribute.Type.Equals(typeof(ConfigurationField.Decimal)))
                {
                    this.SetupDecimal(fieldName, attribute);
                }
                else if (attribute.Type.Equals(typeof(ConfigurationField.String)))
                {
                    this.SetupString(fieldName, attribute);
                }
                else if (attribute.Type.Equals(typeof(ConfigurationField.File)))
                {
                    this.SetupFile(fieldName, attribute);
                }
                else if (attribute.Type.Equals(typeof(ConfigurationField.Table)))
                {
                    this.SetupTable(fieldName, attribute);
                }
            }
        }

        /// <summary>
        /// The setup decimal.
        /// </summary>
        /// <param name="fieldName">
        /// The field name.
        /// </param>
        /// <param name="attribute">
        /// The attribute.
        /// </param>
        /// <exception cref="Exception">
        /// </exception>
        private void SetupDecimal(string fieldName, ConfigurationFieldAttribute attribute)
        {
            if (attribute.IsMandatory)
            {
                if (attribute.DefaultValue == null)
                {
                    throw new Exception("You must provide a DefaultValue for mandatory field of type ConfigurationField.Decimal");
                }

                if (!attribute.DefaultValue.GetType().Equals(typeof(double)))
                {
                    throw new Exception("DefaultValue must have the double type when ConfigurationField.Decimal is used");
                }

                this.fields[fieldName] = new ConfigurationField.Decimal(attribute.IsMandatory, (double)attribute.DefaultValue);
            }
            else
            {
                this.fields[fieldName] = new ConfigurationField.Decimal(attribute.IsMandatory);
            }
        }

        /// <summary>
        /// The setup enum.
        /// </summary>
        /// <param name="fieldName">
        /// The field name.
        /// </param>
        /// <param name="attribute">
        /// The attribute.
        /// </param>
        /// <exception cref="Exception">
        /// </exception>
        private void SetupEnum(string fieldName, ConfigurationFieldAttribute attribute)
        {
            if (attribute.EnumTypeName == null)
            {
                throw new Exception("GenericsType must be given when you use ConfigurationField.Enum as Type");
            }

            IList<string> enumValues = new List<string>(this.container.GetEnumValues(attribute.EnumTypeName));

            // Initial value
            if (attribute.IsMandatory)
            {
                if (attribute.DefaultValue == null)
                {
                    throw new Exception("You must provide a DefaultValue for mandatory field of type ConfigurationField.Enum");
                }

                // Use default value for mandatory field
                this.fields[fieldName] = new ConfigurationField.Enum(
                    attribute.IsMandatory, attribute.EnumTypeName, attribute.DefaultValue.ToString(), enumValues.ToArray());
            }
            else
            {
                // Use "NotSet" for optional field
                this.fields[fieldName] = new ConfigurationField.Enum(attribute.IsMandatory, attribute.EnumTypeName, Constants.NotSet, enumValues.ToArray());
            }
        }

        /// <summary>
        /// The setup file.
        /// </summary>
        /// <param name="fieldName">
        /// The field name.
        /// </param>
        /// <param name="attribute">
        /// The attribute.
        /// </param>
        private void SetupFile(string fieldName, ConfigurationFieldAttribute attribute)
        {
            this.fields[fieldName] = new ConfigurationField.File(attribute.IsMandatory);
        }

        /// <summary>
        /// The setup int.
        /// </summary>
        /// <param name="fieldName">
        /// The field name.
        /// </param>
        /// <param name="attribute">
        /// The attribute.
        /// </param>
        /// <exception cref="Exception">
        /// </exception>
        private void SetupInt(string fieldName, ConfigurationFieldAttribute attribute)
        {
            if (attribute.IsMandatory)
            {
                if (attribute.DefaultValue == null)
                {
                    throw new Exception("You must provide a DefaultValue for mandatory field of type ConfigurationField.Int");
                }

                if (!attribute.DefaultValue.GetType().Equals(typeof(int)))
                {
                    throw new Exception("DefaultValue must have the int type when ConfigurationField.Int is used");
                }

                this.fields[fieldName] = new ConfigurationField.Int(attribute.IsMandatory, (int)attribute.DefaultValue);
            }
            else
            {
                this.fields[fieldName] = new ConfigurationField.Int(attribute.IsMandatory);
            }
        }

        /// <summary>
        /// The setup string.
        /// </summary>
        /// <param name="fieldName">
        /// The field name.
        /// </param>
        /// <param name="attribute">
        /// The attribute.
        /// </param>
        /// <exception cref="Exception">
        /// </exception>
        private void SetupString(string fieldName, ConfigurationFieldAttribute attribute)
        {
            if (attribute.IsMandatory)
            {
                if (attribute.DefaultValue == null)
                {
                    throw new Exception("You must provide a DefaultValue for mandatory field of type ConfigurationField.String");
                }

                if (!attribute.DefaultValue.GetType().Equals(typeof(string)))
                {
                    throw new Exception("DefaultValue must have the double type when ConfigurationField.String is used");
                }

                this.fields[fieldName] = new ConfigurationField.String(attribute.IsMandatory, (string)attribute.DefaultValue);
            }
            else
            {
                this.fields[fieldName] = new ConfigurationField.String(attribute.IsMandatory);
            }
        }

        /// <summary>
        /// The setup table.
        /// </summary>
        /// <param name="fieldName">
        /// The field name.
        /// </param>
        /// <param name="attribute">
        /// The attribute.
        /// </param>
        private void SetupTable(string fieldName, ConfigurationFieldAttribute attribute)
        {
            this.fields[fieldName] = new ConfigurationField.Table(attribute.IsMandatory);
        }

        #endregion
    }
}